package com.urbanski.util;

/**
 * This class represents a simple time in hours, minutes, and seconds.
 * Its intended use is to keep track of times in these increments when
 * the extra code required for Date or Calendar objects is unnecessary.
 * It provides methods to manipulate the time as well as compare it to
 * other time objects.
 * 
 * Any values that are above the standard range (e.g., 64 minutes, 72 seconds)
 * will be automatically converted to the standard representation of the time
 * value (e.g., 1 hour, 5 minutes, 12 seconds). 
 * 
 * However, the behavior of the class is undefined if any value is negative.
 * There may be value in representing "negative" time, but certain
 * methods may not behave as expected.
 * 
 * @author murbanski004
 *
 */
public class SimpleTime implements Comparable<SimpleTime>
{
	protected int hours, minutes, seconds;
	
	/**
	 * Constructs a new SimpleTime object representing the
	 * specified hours, minutes, and seconds
	 * 
	 * @param h the number of hours for the time
	 * @param m the number of minutes for the time
	 * @param s the number of seconds for the time
	 */
	public SimpleTime(int h, int m, int s)
	{
		setTime(h, m, s);
	}
	
	/**
	 * Constructs a new SimpleTime object representing the
	 * specified number of seconds.  Converts to
	 * hours, minutes, and seconds
	 * @param sec the number of seconds for the time
	 */
	public SimpleTime(int sec)
	{
		fromSeconds(sec);
	}
	
	/**
	 * Constructs a new SimpleTime object with the same time
	 * as the specified object
	 * 
	 * @param t the SimpleTime off of which to base this SimpleTime
	 */
	public SimpleTime(SimpleTime t)
	{
		hours = t.hours;
		minutes = t.minutes;
		seconds = t.seconds;
	}
	
	/**
	 * Returns the hour portion of this SimpleTime
	 * 
	 * @return the hour portion of this SimpleTime
	 */
	public int getHours()
	{
		return hours;
	}
	
	/**
	 * Sets the hour portion of this SimpleTime
	 * 
	 * @param hours the hour portion of this SimpleTime
	 */
	public void setHours(int hours)
	{
		setTime(hours, minutes, seconds);
	}
	
	/**
	 * Returns the minute portion of this SimpleTime
	 * 
	 * @return the minute portion of this SimpleTime
	 */
	public int getMinutes()
	{
		return minutes;
	}
	
	/**
	 * Sets the minute portion of this SimpleTime
	 * 
	 * @param minutes the minute portion of this SimpleTime
	 */
	public void setMinutes(int minutes)
	{
		setTime(hours, minutes, seconds);
	}
	
	/**
	 * Returns the seconds portion of this SimpleTime
	 * 
	 * @return the seconds portion of this SimpleTime
	 */
	public int getSeconds()
	{
		return seconds;
	}

	/**
	 * Sets the second portion of this SimpleTime
	 * 
	 * @param seconds the second portion of this SimpleTime
	 */
	public void setSeconds(int seconds)
	{
		setTime(hours, minutes, seconds);
	}
	
	/**
	 * Gets the total number of seconds for this SimpleTime.
	 * Total number of seconds is equal to 
	 * 3600 * hours + 60 * minutes + seconds
	 * 
	 * @return the total number of seconds for this SimpleTime
	 */
	public int toSeconds()
	{
		return hours * 3600 + minutes * 60 + seconds;
	}
	
	/**
	 * Sets the time to be the specified number of seconds.
	 * Converts to hours, minutes, and seconds.
	 * 
	 * @param sec the number of seconds for the time
	 */
	public void fromSeconds(int sec)
	{
		minutes = sec / 60;
		hours = minutes / 60;
		seconds = sec % 60;
		minutes = minutes % 60;
	}
	
	/**
	 * Sets the time to the specified hours, minutes,
	 * and seconds
	 * 
	 * @param h the number of hours for the time
	 * @param m the number of minutes for the time
	 * @param s the number of seconds for the time
	 */
	public void setTime(int h, int m, int s)
	{
		fromSeconds(h * 3600 + m * 60 + s);
	}
	
	/**
	 * Adds the specified number of hours, minutes, and seconds
	 * to this time
	 * 
	 * @param h the number of hours to add
	 * @param m the number of minutes to add
	 * @param s the number of seconds to add
	 */
	public void addTime(int h, int m, int s)
	{
		addSeconds(h * 3600 + m * 60 + s);
	}
	
	/**
	 * Adds the specified number of seconds to this time
	 * 
	 * @param sec the number of seconds to add
	 */
	public void addSeconds(int sec)
	{
		fromSeconds(toSeconds() + sec);
	}
	/**
	 * Adds the specified number of minutes to this time
	 * 
	 * @param min the number of minutes to add
	 */
	public void addMinutes(int min)
	{
		fromSeconds(toSeconds() + 60 * min);
	}
	/**
	 * Adds the specified number of hours to this time
	 * 
	 * @param hr the number of hours to add
	 */
	public void addHours(int hr)
	{
		fromSeconds(toSeconds() + 3600 * hr);
	}
	
	/**
	 * Adds the time represented by the specified SimpleTime to this time
	 * 
	 * @param t the time to add
	 */
	public void addTime(SimpleTime t)
	{
		fromSeconds(t.toSeconds() + toSeconds());
	}
	
	/**
	 * Subtracts the specified number of hours, minutes, and seconds
	 * from this time
	 * 
	 * @param h the number of hours to subtract
	 * @param m the number of minutes to subtract
	 * @param s the number of seconds to subtract
	 */
	public void subtractTime(int h, int m, int s)
	{
		subtractSeconds(h * 3600 + m * 60 + s);
	}
	
	/**
	 * Subtracts the specified number of seconds from this time
	 * 
	 * @param sec the number of seconds to subtract
	 */
	public void subtractSeconds(int sec)
	{
		fromSeconds(toSeconds() - sec);
	}
	
	/**
	 * Subtracts the specified number of minutes from this time
	 * 
	 * @param min the number of minutes to subtract
	 */
	public void subtractMinutes(int min)
	{
		fromSeconds(toSeconds() - 60 * min);
	}
	
	/**
	 * Subtracts the specified number of hours from this time
	 * 
	 * @param sec the number of hours to subtract
	 */
	public void subtractHours(int hr)
	{
		fromSeconds(toSeconds() - 3600 * hr);
	}
	
	/**
	 * Subtracts the time represented by the specified SimpleTime from this time
	 * 
	 * @param t the time to subtract
	 */
	public void subtractTime(SimpleTime t)
	{
		fromSeconds(toSeconds() - t.toSeconds());
	}
	
	/**
	 * Finds the difference in time between this time and the specified time;
	 * simply, the value returned equals (this - t).  This method does
	 * not modify either time.
	 * 
	 * @param t the time to subtract from this time
	 * @return the difference in time between this time and the specified time
	 */
	public SimpleTime difference(SimpleTime t)
	{
		return new SimpleTime(toSeconds() - t.toSeconds());
	}
	
	/**
	 * Returns a String representation of this time in the form
	 * "hh:mm:ss".  For values less than 10, the leading zero
	 * is always added, e.g., 05:04:03.  If the hours portion
	 * of the time is zero, it is still returned, e.g.,
	 * 00:45:23.
	 * 
	 * @return a String representation of this time
	 */
	public String toString()
	{
		return StringUtils.fixLengthBefore(hours, 2, '0') + ':' +
			   StringUtils.fixLengthBefore(minutes, 2, '0') + ':' +
			   StringUtils.fixLengthBefore(seconds, 2, '0');
	}
	
	/**
	 * Returns a String representation of this time in the specified
	 * format.  This method can be used to both wrap this time in a
	 * custom String as well as combine larger time components into
	 * smaller ones, as described below.
	 * 
	 * Placeholders in the format String can be one of the following:
	 * h, hh, m mm, s, ss
	 * 
	 * If the one-character variant is used, then, if that portion of
	 * the time (e.g. hours) is less than 10, it will not be padded
	 * with a zero.  This has no effect if the value is greater than
	 * or equal to 10.
	 * 
	 * If no components are specified, then the format String is simply
	 * returned as-is.
	 * 
	 * The sequences can be escaped with a backslash %.  For example, the format
	 * String "\s" would not be converted.  A backslash can be escaped by another
	 * backslash, "\\".  All occurrences of h, m, and s must be escaped.  Backslashes
	 * that do not occur before h, m, and s are treated as literals.
	 * 
	 * @param format the format String for this time
	 * @return a String representation of this time in the specified
	 * format
	 */
	
	/*
	 * Time: 01:04:47
	 * hh:mm:ss = 01:04:47
	 * h:mm:ss = 1:04:47
	 * mmss = 6447
	 * h \h, m \m, s \s = 1 h, 4 m, 47 s
	 * 
	 */
	
	public String toString(String format)
	{
		StringBuilder sb = new StringBuilder();
		
		for (int i = 0; i < format.length(); i++)
		{
			char c = format.charAt(i);
			if (c == '\\')
			{
				if (i == format.length() - 1)
				{
					sb.append(c);
				}
				else
				{
					char c1 = format.charAt(++i);
					if (c1 == 'h' || c1 == 'm' || c1 == 's')
					{
						sb.append(c1);
					}
					else
					{
						sb.append(c).append(c1);
					}
				}
			}
			else if (c == 'h')
			{
				if (i == format.length() - 1)
				{
					sb.append(hours);
				}
				else
				{
					char c1 = format.charAt(++i);
					if (c1 == 'h')
					{
						sb.append(StringUtils.fixLengthBefore(hours, 2, '0'));
					}
					else
					{
						sb.append(hours).append(c1);
					}
				}
			}
			else if (c == 'm')
			{
				if (i == format.length() - 1)
				{
					sb.append(minutes);
				}
				else
				{
					char c1 = format.charAt(++i);
					if (c1 == 'm')
					{
						sb.append(StringUtils.fixLengthBefore(minutes, 2, '0'));
					}
					else
					{
						sb.append(minutes).append(c1);
					}
				}
			}
			else if (c == 's')
			{
				if (i == format.length() - 1)
				{
					sb.append(seconds);
				}
				else
				{
					char c1 = format.charAt(++i);
					if (c1 == 's')
					{
						sb.append(StringUtils.fixLengthBefore(seconds, 2, '0'));
					}
					else
					{
						sb.append(seconds).append(c1);
					}
				}
			}
			else
			{
				sb.append(c);
			}
		}
		
		return sb.toString();
	}
	
	/**
	 * Returns a new SimpleTime object with the time represented by the
	 * specified String.  The String may be in h:m:s, m:s, or seconds
	 * only format.  See the following examples for the SimpleTime value
	 * returned by this method for the given input:
	 * 
	 * time		returned value
	 * -----------------------
	 * 7		00:00:07
	 * 156		00:02:36
	 * 5:14		00:05:14
	 * 7:24:00	07:24:00
	 * 1:2:3	01:02:03
	 * 
	 * @param time the String to parse
	 * @return a new SimpleTime object with the time represented by the
	 * specified String
	 */
	public static SimpleTime parseTime(String time)
	{
		String[] arr = time.split(":", 3);
		int h = 0, m = 0, s = 0;
		
		s = Integer.parseInt(arr[arr.length - 1]);
		
		if (arr.length >= 2)
		{
			m = Integer.parseInt(arr[arr.length - 2]);
			
			if (arr.length == 3)
			{
				h = Integer.parseInt(arr[arr.length - 3]);
			}
		}

		return new SimpleTime(h, m, s);
	}

	@Override
	public int compareTo(SimpleTime t)
	{
		return toSeconds() - t.toSeconds();
	}

	@Override
	public int hashCode()
	{
		return toSeconds();
	}

	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof SimpleTime))
			return false;
		SimpleTime t = (SimpleTime) obj;
		return toSeconds() == t.toSeconds();
	}
	
//	public static void main(String[] args)
//	{
//		SimpleTime t = new SimpleTime(1, 4, 47);
//		System.out.println(t.toString("h \\h, m \\m, s \\s"));
//	}
}
